/***************************************************************************
 *   Temporary file and memory buffer SDL_RWops stream implimentation      *
 *                                                                         *
 *   Copyright (C) 2004 by Tyler Montbriand                                *
 *   tsm@accesscomm.ca                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <SDL/SDL_error.h>
#include "SDL_rwtmp.h"
#include "SDL_rwlib_internal.h"

#define PKGNAME TMP

#define SDL_TMP_BLOCK 4096 // Memory block size

RWFUNC_SEEK(PKGNAME);
RWFUNC_READ(PKGNAME);
RWFUNC_WRITE(PKGNAME);
RWFUNC_CLOSE(PKGNAME);

SDL_RWops *SDL_RWTmp()
{
  SDL_RWops *rw;

  rw=SDL_AllocRW();
  if(rw==NULL)
  {
    SDL_SetError("Out of memory");
    return(NULL);
  }

  memset(rw,0,sizeof(SDL_RWops));
  RW_INITFUNC(rw,PKGNAME);
  rw->hidden.mem.base=NULL;
  rw->hidden.mem.here=NULL;
  rw->hidden.mem.stop=NULL;
  return(rw);
}

RWFUNC_SEEK(PKGNAME)
  /*(SDL_RWops *ctx, int offset, int whence)*/
{
  Uint8 *off;

  switch(whence)
  {
  case SEEK_SET: /* Seek from beginning of buffer */
    off=(ctx->hidden.mem.base)+offset;
    break;
  case SEEK_CUR: /* Seek from current position */
    off=(ctx->hidden.mem.here)+offset;
    break;
  case SEEK_END: /* Seek from end of file */
    off=(ctx->hidden.mem.stop)+offset;
    break;
  default:
    SDL_SetError("sdl_tmp_seek:  whence should be SEEK_SET, SEEK_CUR, SEEK_SET");
    return(-1);
  }

  if(off>(ctx->hidden.mem.stop))      off=ctx->hidden.mem.stop;
  else if(off<(ctx->hidden.mem.base)) off=ctx->hidden.mem.base;

  ctx->hidden.mem.here=off;

  return((int)(long int)(off-(ctx->hidden.mem.base)));
}

RWFUNC_READ(PKGNAME)
  /*(SDL_RWops *ctx, void *ptr, int size, int maxnum)*/
{
  long int left=(long int)((ctx->hidden.mem.stop)-(ctx->hidden.mem.here));
  long int blocks=(left/size);

  if(blocks==0)     return(0);

  if(blocks>maxnum) blocks=maxnum;

  memcpy(ptr,ctx->hidden.mem.here,blocks*size);
  ctx->hidden.mem.here+=(blocks*size);
  return(blocks);
}

static int sdl_tmp_resize(SDL_RWops *ctx, long int more)
{
  // get original parameters
  Uint8 *base=ctx->hidden.mem.base,*nbase;
  long int pos=(long int)(ctx->hidden.mem.here-ctx->hidden.mem.base);
  long int len=(long int)(ctx->hidden.mem.stop-ctx->hidden.mem.base);
  long int nlen=len+more;

  if(nlen%SDL_TMP_BLOCK)
  {
    nlen-=nlen%SDL_TMP_BLOCK;
    nlen+=SDL_TMP_BLOCK;
  }

  nbase=realloc(base,nlen);

  if(nbase==NULL)
  {
    SDL_SetError("sdl_tmp_resize:  Out of memory");
    return(-1);
  }

  ctx->hidden.mem.base=nbase;
  ctx->hidden.mem.here=nbase+pos;
  ctx->hidden.mem.stop=nbase+nlen;

  return(0);
}

RWFUNC_WRITE(PKGNAME)
  /*(SDL_RWops *ctx, const void *ptr, int size, int maxnum)*/
{
  long int left=(long int)((ctx->hidden.mem.stop)-(ctx->hidden.mem.here));

  if(left<(size*maxnum)) // Attempt to expand memory before copying
  {
    int more=(size*maxnum)-left;

    if(sdl_tmp_resize(ctx,more)<0) // Cannot resize, but can still write
      maxnum=(left/size);
  }

  // Copy all in one go!
  if(maxnum) memcpy(ctx->hidden.mem.here,ptr,maxnum*size);
  return(maxnum);
}

RWFUNC_CLOSE(PKGNAME)
  /*(SDL_RWops *ctx)*/
{
  int retval=0;
  if(ctx==NULL)
  {
    SDL_SetError("sdl_tmp_close: NULL context");
    return(-1);
  }
  else if(RW_TYPE(ctx)!=RW_TYPENUM(PKGNAME))
  {
    SDL_SetError("sdl_tmp_close: context is not RW_TYPENUM(PKGNAME)");
    return(-1);
  }

  if(ctx->hidden.mem.base!=NULL) free(ctx->hidden.mem.base);

  SDL_FreeRW(ctx);
  return(retval);  
}

int SDL_RWTmp_glob(SDL_RWops *ctx,SDL_RWops *src, int size, int maxnum)
{
  int blocks=0;

  if(ctx->type!=RW_TYPENUM(PKGNAME))
  {
    SDL_SetError("SDL_RWTmp_glob:  first parameter must be SDL_RWTmp buffer");
    return(-1);
  }

  while(blocks<maxnum)
  {
//    void *cur;
    long int left=(long int)((ctx->hidden.mem.stop)-(ctx->hidden.mem.here));

    if(left<size)
    {
      if(sdl_tmp_resize(ctx,left-size)<0)
        return(blocks);
    }

    if(SDL_RWread(src,ctx->hidden.mem.here,size,1)!=1)
      return(blocks);
    ctx->hidden.mem.here+=size;
    blocks++;
  }

  return(blocks);
}

SDL_RWops *SDL_RWTmpFile()
{
  SDL_RWops *rw;
  FILE *fp=tmpfile();
  if(fp==NULL) return(NULL);
  rw=SDL_RWFromFP(fp,1);
  if(rw==NULL)
  {
    fclose(fp);
    return(NULL);
  }

  return(rw);
}
